﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Config_App
{
    /// <summary>
    /// Benutzersteuerelement für Zeichenerkennung
    /// </summary>
    public partial class _uc_OCR : UserControl
    {
        private Size _Train_size = new Size(150, 150);
        private static string[] fonts = new string[] { "Arial", "Courier", "Tahoma", "Times New Roman", "Verdana" };
        public bool[] regularFonts = new bool[fonts.Length];
        public bool[] italicFonts = new bool[fonts.Length];
        private bool fire_cb_event = true;
        private int[,][] data;
        public int initialReceptorsCount = 500;
        public int receptorsCount = 100;

        public IVisual.Tools.OCR.Receptors receptors{get; set;}
        public float[][] input{get; set;}
        public float[][] output{get; set;}

        public _uc_OCR()
		{
			InitializeComponent();

            receptors = new IVisual.Tools.OCR.Receptors();
			initialReceptorsBox.Text = initialReceptorsCount.ToString();
			receptorsBox.Text = receptorsCount.ToString();

			UpdateAvailableFonts();
		}

        /// <summary>
        /// Aktualisert die internen strukturen auf die GUI Controls
        /// </summary>
        public void UPDATE_ALL_DATA()
        {
            this.fire_cb_event = false;

            this.arialCheck.Checked = regularFonts[0];
            this.courierCheck.Checked = regularFonts[1];
            this.tahomaCheck.Checked = regularFonts[2];
            this.timesCheck.Checked = regularFonts[3];
            this.verdanaCheck.Checked = regularFonts[4];

            this.arialItalicCheck.Checked = italicFonts[0];
            this.courierItalicCheck.Checked = italicFonts[1];
            this.tahomaItalicCheck.Checked = italicFonts[2];
            this.timesItalicCheck.Checked = italicFonts[3];
            this.verdanaItalicCheck.Checked = italicFonts[4];

            this.initialReceptorsBox.Text = this.initialReceptorsCount.ToString();
            this.receptorsBox.Text = this.receptorsCount.ToString();

            this.fire_cb_event = true;
        }
        
        /// <summary>
        /// aktualisert die GUI Controls auf die internen strukturen
        /// </summary>
        private void UpdateAvailableFonts()
        {
            regularFonts[0] = arialCheck.Checked;
            regularFonts[1] = courierCheck.Checked;
            regularFonts[2] = tahomaCheck.Checked;
            regularFonts[3] = timesCheck.Checked;
            regularFonts[4] = verdanaCheck.Checked;

            italicFonts[0] = arialItalicCheck.Checked;
            italicFonts[1] = courierItalicCheck.Checked;
            italicFonts[2] = tahomaItalicCheck.Checked;
            italicFonts[3] = timesItalicCheck.Checked;
            italicFonts[4] = verdanaItalicCheck.Checked;

            this._cmd_gen_cfg.Enabled =
                regularFonts[0] | regularFonts[1] | regularFonts[2] | regularFonts[3] | regularFonts[4] |
                italicFonts[0] | italicFonts[1] | italicFonts[2] | italicFonts[3] | italicFonts[4];
        }

        /// <summary>
        /// Ertsellt die Daten welche zu lernen sind.
        /// </summary>
        private void GenerateLearningData()
        {
            int objectsCount = 26;
            int featuresCount = receptors.Count;
            int variantsCount = 0;
            int fontsCount = fonts.Length * 2;
            int i, j, k, font, v = 0;
            bool italic;

            // Schriftarten und Typen aufzählen
            for (i = 0; i < fonts.Length; i++)
            {
                variantsCount += (regularFonts[i]) ? 1 : 0;
                variantsCount += (italicFonts[i]) ? 1 : 0;
            }

            if (variantsCount == 0)
                return;

            // datenarray anlegen
            data = new int[objectsCount, featuresCount][];
            // daten initialisieren
            for (i = 0; i < objectsCount; i++)
            {
                for (j = 0; j < featuresCount; j++)
                {
                    data[i, j] = new int[variantsCount];
                }
            }

            // für jede schrift
            for (j = 0; j < fontsCount; j++)
            {
                font = j >> 1;
                italic = ((j & 1) != 0);

                // unerwünschte Schriften nicht durchlaufen
                if (((italic) && (!italicFonts[font])) ||
                    ((!italic) && (!regularFonts[font])))
                {
                    continue;
                }

                // für jedes zeichen
                for (i = 0; i < objectsCount; i++)
                {
                    Bitmap img = _cls_PB_Stub.DrawLetter((char)((int)'A' + i), fonts[font], 90, italic, this._Train_size);

                    // Rezeptorenstatus ermitteln
                    int[] state = receptors.GetReceptorsState(img);

                    // diesen dann kopieren
                    for (k = 0; k < featuresCount; k++)
                    {
                        data[i, k][v] = state[k];
                    }
                }

                v++;
            }
        }

        /// <summary>
        /// Reduziert die daten, idem doppelte entfert werden
        /// </summary>
        private void RemoveLearningDuplicates()
        {
            if (data == null)
                return;

            int objectsCount = data.GetLength(0);
            int featuresCount = data.GetLength(1);
            int variantsCount = data[0, 0].Length;

            int i, j, k, s;
            int[] item;

            // checksumme für jedes zeichen und rezeptor
            int[,] checkSum = new int[objectsCount, featuresCount];

            // für jedes zeichen
            for (i = 0; i < objectsCount; i++)
            {
                // für jeden rezeptor
                for (j = 0; j < featuresCount; j++)
                {
                    item = data[i, j];
                    s = 0;

                    // für jede variante
                    for (k = 0; k < variantsCount; k++)
                    {
                        s |= item[k] << k;
                    }

                    checkSum[i, j] = s;
                }
            }

            // welche rezeptoren sind überflüssig?
            bool[] remove = new bool[featuresCount];

            // über alle rezeptoren laufen
            for (i = 0; i < featuresCount - 1; i++)
            {
                // wenn der rezeptor bereits zum löschen markiert ist, überspringen
                if (remove[i] == true)
                    continue;

                // jeden Rezeptor mit jedem anderen vergleichen
                for (j = i + 1; j < featuresCount; j++)
                {
                    // standard ist löschen (opt-out)
                    remove[j] = true;

                    // prüfsummen aller vergleichen
                    for (k = 0; k < objectsCount; k++)
                    {
                        if (checkSum[k, i] != checkSum[k, j])
                        {
                            // sie sind doch unterschliedlich! Doch nicht löschen
                            remove[j] = false;
                            break;
                        }
                    }
                }
            }

            // zu speichernde Rezeporten abzählen
            int receptorsToSave = 0;
            for (i = 0; i < featuresCount; i++)
                receptorsToSave += (remove[i]) ? 0 : 1;

            // daten aufgrund der rezeptoren filtern
            int[,][] newData = new int[objectsCount, receptorsToSave][];
            IVisual.Tools.OCR.Receptors newReceptors = new IVisual.Tools.OCR.Receptors();

            k = 0;
            // für jeden rezeptor
            for (j = 0; j < featuresCount; j++)
            {
                if (remove[j])
                    continue;

                // für jedes zeichen
                for (i = 0; i < objectsCount; i++)
                {
                    newData[i, k] = data[i, j];
                }
                newReceptors.Add(receptors[j]);
                k++;
            }

            // neue daten setzen
            data = newData;
            receptors = newReceptors;
        }

        /// <summary>
        /// Filtert die Lerndaten. Verfahren basiert auf dem entfernen schlechter rezeptoren
        /// </summary>
        private void FilterLearningData()
        {
            if (data == null)
                return;

            int objectsCount = data.GetLength(0);
            int featuresCount = data.GetLength(1);
            int variantsCount = data[0, 0].Length;
            int i, j, k, v;
            int[] item;

            // wurde bereits gerfiltert???
            if (receptorsCount >= featuresCount)
                return;

            int[] outerCounters = new int[2];
            int[] innerCounters = new int[2];
            double ie, oe;

            double[] usabilities = new double[featuresCount];

            // für jeden rezeptor
            for (j = 0; j < featuresCount; j++)
            {
                // outer counters leeren
                Array.Clear(outerCounters, 0, 2);

                ie = 0;
                // für jedes zeichen
                for (i = 0; i < objectsCount; i++)
                {
                    // inner counters leeren
                    Array.Clear(innerCounters, 0, 2);
                    // item auslesen
                    item = data[i, j];

                    // für jede variante
                    for (k = 0; k < variantsCount; k++)
                    {
                        v = item[k];

                        innerCounters[v]++;
                        outerCounters[v]++;
                    }

                    // Entropy für inner Counter berechnen
                    ie += Entropy(innerCounters, variantsCount);
                }

                // Durchschnitt der inneren Entropy
                ie /= objectsCount;
                // outer Entropy
                oe = Entropy(outerCounters, objectsCount * variantsCount);
                // rezeptor nutzbarkeit
                usabilities[j] = (1.0 - ie) * oe;
            }

            // kopiere nutzbarkeit und sortiere diese
            double[] temp = (double[])usabilities.Clone();
            Array.Sort(temp);
            // akzeptierte nutzbarkeit erstellen
            double accaptableUsability = temp[featuresCount - receptorsCount];

            // filtere daten, welche eine schlechtere nutzbarkeit als die aktzeptierte aufweisen
            int[,][] newData = new int[objectsCount, receptorsCount][];
            IVisual.Tools.OCR.Receptors newReceptors = new IVisual.Tools.OCR.Receptors();

            k = 0;
            // für jeden rezeptor
            for (j = 0; j < featuresCount; j++)
            {
                if (usabilities[j] < accaptableUsability)
                    continue;

                // für jedes zeichen
                for (i = 0; i < objectsCount; i++)
                {
                    newData[i, k] = data[i, j];
                }
                newReceptors.Add(receptors[j]);

                if (++k == receptorsCount)
                    break;
            }

            // setze daten neu
            data = newData;
            receptors = newReceptors;
        }

        /// <summary>
        /// parst die eingabewerte aus der GUI
        /// </summary>
        private void GetReceptorsCount()
        {
            try
            {
                initialReceptorsCount = Math.Max(25, Math.Min(5000, int.Parse(initialReceptorsBox.Text)));
                receptorsCount = Math.Max(10, Math.Min(initialReceptorsCount, int.Parse(receptorsBox.Text)));
            }
            catch (Exception)
            {
                initialReceptorsCount = 500;
                receptorsCount = 100;
            }
            initialReceptorsBox.Text = initialReceptorsCount.ToString();
            receptorsBox.Text = receptorsCount.ToString();
        }

        /// <summary>
        /// erzeugt neue rezeptoren und löscht die alten
        /// </summary>
        private void GenerateReceptors()
        {
            // vorheriger rezeptoren löschen
            receptors.Clear();
            // rezeptor-arena größe setzten
            receptors.AreaSize = this._Train_size;
            // erzeuge neue rezeptoren
            receptors.Generate(initialReceptorsCount);
            // aktuellen rezeptorencount setzten
            currentReceptorsBox.Text = initialReceptorsCount.ToString();
        }

        /// <summary>
        /// Event für die Font CBs
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FontCheck_CheckedChanged(object sender, EventArgs e)
        {
            if (!this.fire_cb_event)
                return;
            UpdateAvailableFonts();
        }

        /// <summary>
        /// Berechnet dei Entropy von werten
        /// </summary>
        /// <param name="values"></param>
        /// <param name="total"></param>
        /// <returns></returns>
        private double Entropy(int[] values, int total)
        {
            int n = values.Length;
            double e = 0;
            double p;

            // für jeden wert
            for (int i = 0; i < n; i++)
            {
                // berechne werte-warscheinlichkeit
                p = (double)values[i] / total;
                // entropy berechnen
                if (p != 0)
                    e += (-p * Math.Log(p, 2));
            }
            return e;
        }

        /// <summary>
        /// Erzeugt die komplette konfiguration. Rezeptoren, filtern, ablegen....
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _cmd_gen_cfg_Click(object sender, EventArgs e)
        {
            this._cmd_gen_cfg.Enabled = false;

            GetReceptorsCount();
            GenerateReceptors();

            GenerateLearningData();
            
            RemoveLearningDuplicates();
            FilterLearningData();
            //ShowLearningData();

            currentReceptorsBox.Text = this.receptors.Count.ToString();

            int objectsCount = data.GetLength(0);
            int featuresCount = data.GetLength(1);
            int variantsCount = data[0, 0].Length;
            int i, j, k, n;

            // erzeuge mögliche ausgabe
            float[][] possibleOutputs = new float[objectsCount][];

            for (i = 0; i < objectsCount; i++)
            {
                possibleOutputs[i] = new float[objectsCount];
                for (j = 0; j < objectsCount; j++)
                {
                    possibleOutputs[i][j] = (i == j) ? 0.5f : -0.5f;
                }
            }

            // trainigsdaten erzeugen
            this.input = new float[objectsCount * variantsCount][];
            this.output = new float[objectsCount * variantsCount][];
            float[] ins;

            // für jede variante
            for (j = 0, n = 0; j < variantsCount; j++)
            {
                // für jedes zeichen
                for (i = 0; i < objectsCount; i++, n++)
                {
                    // ausgabe vorbereiten
                    input[n] = ins = new float[featuresCount];

                    // für jeden rezeptor
                    for (k = 0; k < featuresCount; k++)
                    {
                        ins[k] = (float)data[i, k][j] - 0.5f;
                    }

                    // setzte ausgabe
                    output[n] = possibleOutputs[i];
                }
            }

            this._l_tipp.Text = "Empfehlungen:\r\nLernrate: 0,6\r\nMomentum: 0,00\r\nAlpha: 2,00\r\nSchichten: 2\r\nNeuronen: " + data.GetLength(0) + "\r\nFehlerlimit: 0,5\r\nBipolarSigmoid";

            this._cmd_gen_cfg.Enabled = true;
        }
    }
}
